﻿// -----------------------------------------------------------------------
// <copyright file="ExpressionVisitor.cs" company="Spardo, LLC">
// Copyright (c) 2011 SPardo, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a 
// copy of this software and associated documentation files (the "Software"), 
// to deal in the Software without restriction, including without limitation 
// the rights to use, copy, modify, merge, publish, distribute, sublicense, 
// and/or sell copies of the Software, and to permit persons to whom the 
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included 
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
// DEALINGS IN THE SOFTWARE.
// </copyright>
// -----------------------------------------------------------------------

namespace DotNet37Signals.Client.Highrise.Linq
{
    using System;
    using System.IO;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Runtime.Serialization;
    using System.Text;
    using System.Web;

    /// <summary>
    /// TODO: Update summary.
    /// </summary>
    internal class ExpressionVisitor
    {
        public string Visit(Expression expression)
        {
            var sb = new StringBuilder();
            this.Visit(expression, new StringWriter(sb));
            return sb.ToString();
        }

        void Visit(Expression expression, TextWriter writer)
        {
            switch (expression.NodeType)
            {
                case ExpressionType.AndAlso:
                case ExpressionType.And:
                    this.AndOr((BinaryExpression)expression, writer);
                    break;

                case ExpressionType.Equal:
                    this.Equal((BinaryExpression)expression, writer);
                    break;

                case ExpressionType.MemberAccess:
                    this.Member((MemberExpression)expression, writer);
                    break;

                case ExpressionType.Constant:
                    this.Constant((ConstantExpression)expression, writer);
                    break;

                case ExpressionType.Call:
                    this.Call((MethodCallExpression)expression, writer);
                    break;

                case ExpressionType.Or:
                case ExpressionType.OrElse:
                case ExpressionType.NotEqual:
                case ExpressionType.GreaterThan:
                case ExpressionType.GreaterThanOrEqual:
                case ExpressionType.LessThan:
                case ExpressionType.LessThanOrEqual:
                default:
                    throw new NotSupportedException(string.Format("Expression type [{0}] is not supported", expression.NodeType.ToString()));
            }
        }

        void AndOr(BinaryExpression expression, TextWriter writer)
        {
            this.Visit(expression.Left, writer);
            this.Visit(expression.Right, writer);
        }

        void Equal(BinaryExpression expression, TextWriter writer)
        {
            writer.Write("&");

            this.Visit(expression.Left, writer);

            switch (expression.NodeType)
            {
                case ExpressionType.Equal:
                    writer.Write("=");
                    break;
            }

            this.Visit(expression.Right, writer);
        }

        void Member(MemberExpression expression, TextWriter writer)
        {
            if (expression.Expression.NodeType == ExpressionType.Parameter)
            {
                var output = string.Empty;

                var attrs = expression.Member.GetCustomAttributes(typeof(DataMemberAttribute), true);
                var member = attrs.First();

                if (member != null)
                    output = ((DataMemberAttribute)member).Name;
                else
                    output = expression.Member.Name;

                writer.Write("criteria[{0}]", output);
            }
            else
            {
                var member = Expression.Lambda<Func<object>>(expression).Compile();
                writer.Write(member());
            }
        }

        void Constant(ConstantExpression expression, TextWriter writer)
        {
            var constant = Expression.Lambda<Func<object>>(expression).Compile();
            writer.Write(HttpUtility.UrlEncode(constant().ToString()));
        }

        void Call(MethodCallExpression expression, TextWriter writer)
        {
            if (expression.Arguments.Count > 0)
                throw new ArgumentException("Methods with arguments are not supported");
            
            var result = Expression.Lambda(expression).Compile().DynamicInvoke();

            writer.Write(result.ToString());
        }
    }
}
